package com.xy.job.admin.core.schedule

import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import java.time.temporal.Temporal
import java.time.temporal.TemporalUnit
import java.util.concurrent.DelayQueue
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread

class Scheduler(
    /**
     * 一个时间槽的刻度
     */
    slotTick: Long,
    /**
     * 时间轮的时间槽个数
     */
    slotSize: Int,
) {
    @Volatile
    private var running = true
    private val delayQueue = DelayQueue<ScheduleTaskList>()
    private val timeWheel = TimeWheel(slotTick, slotSize, delayQueue)
    private val workerThreadPool: ExecutorService = Executors.newFixedThreadPool(4)
    private val bossThread: Thread = thread(start = false) {
        while (running) {
            advanceClock()
            TimeUnit.SECONDS.sleep(1)
        }
    }

    fun start() {
        bossThread.start()
    }

    fun stop() {
        running = false
        workerThreadPool.shutdown()
    }

    fun addTask(jobName: String, scheduleAt: Temporal, task: Runnable) {
        val delayMillis = LocalDateTime.now().until(scheduleAt, ChronoUnit.MILLIS)
        val scheduleTask = ScheduleTask(delayMillis, jobName, task)
        addTask(scheduleTask)
    }

    fun addTask(jobName: String, delay: Long, timeUnit: TemporalUnit, task: Runnable) {
        val now = LocalDateTime.now()
        val scheduleAt = now.plus(delay, timeUnit)
        addTask(jobName, scheduleAt, task)
    }

    private fun addTask(scheduleTask: ScheduleTask) {
        if (!timeWheel.addTask(scheduleTask)) {
            // 已过期的任务会添加失败，需要直接执行
            workerThreadPool.submit(scheduleTask)
        }
    }

    private fun advanceClock() {
        try {
            val timerTaskList = delayQueue.poll()
            if (timerTaskList != null) {
                // 能拿到任务就可以推进时间
                timeWheel.advanceTo(timerTaskList.expiration.get())
                // 执行过期任务（包含降级操作）
                timerTaskList.flush(::addTask)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}